// 错误处理练习

const util = @import("util.zig"); // 自定义模块要带".zig"
const std = @import("std");

// NOTE:
// 目前 zig 的错误处理方案笔者认为是比较简陋的，在 zig 中错误类型很像 enum，
// 这导致错误类型无法携带有效的 payload，你只能通过错误的名称来获取有效的信息。

// 在编译时，zig 会为每个错误名称分配一个大于 0 的整数值，这个值可以通过 @intFromError 获取。
// 但不建议过分依赖具体的数值，因为错误与数值的映射关系可能会随源码变动。
// 错误对应的整数值默认使用 u16 类型，即最多允许存在 65534 种不同的错误。
// 从 0.12 开始，编译时可以通过 --error-limit [num] 指定错误的最大数量，
// 这样就会使用能够表示所有错误值的最少位数的整数类型。

// anyerror 指的是全局的错误集，它包含编译单元中的所有错误，是所有其他错误集的超集。

const FileErr = error{
    AcccessDenied,
    OutOfMemory, // 同一错误可被包含中不同错误集中, LXQ:理解为error的枚举共用同一个命名空间
    FileNotFound,
};

const AllocErr = error{
    OutOfMemory, // 同一错误可被包含中不同错误集中
};

fn foo(err: AllocErr) FileErr {
    return err; // 错误可以从子集隐式转换到超集
}

const maxInt = std.math.maxInt;

/// 字符转数字
///
/// @param buf 字符串
///
/// @param radix 进制，如2、8、10、16
///
/// @returns 转换后数字，或错误
///
/// @seealso
pub fn parseU64(buf: []const u8, radix: u8) !u64 { // 在普通类型的前面增加一个 ! 就是代表这个类型变成错误联合类型
    var x: u64 = 0;

    for (buf) |c| {
        const n = char2Num(c);
        if (n >= radix) {
            return error.InvalidChar;
        }

        // x *= radix
        var ov = @mulWithOverflow(x, radix);
        if (ov[1] != 0) return error.OverFlow;

        // x += n
        ov = @addWithOverflow(ov[0], n);
        if (0 != ov[1]) return error.OverFlow;
        x = ov[0];
    }

    return x;
}

fn char2Num(c: u8) u8 {
    return switch (c) {
        '0'...'9' => c - '0',
        'A'...'Z' => c - 'A' + 10,
        'a'...'z' => c - 'a' + 10,
        else => maxInt(u8),
    };
}

pub fn run() void {
    const err = foo(AllocErr.OutOfMemory);
    if (FileErr.OutOfMemory == err) {
        util.debugln("错误: {any}", .{err});
    }

    // catch 用于发生错误时提供一个默认值
    // NOTE: catch 运算符右侧必须是一个与其左侧函数返回的错误联合类型展开后的类型 (也就是除了错误类型外的类型) 一致，
    //       或者是一个 noreturn(例如 panic) 的语句。
    const num = parseU64("123", 10) catch 3; // 不进行错误处理，则用：catch{}。但这时不能赋值，因数必须回返相关类型。
    util.debugln("num: {}", .{num});

    // 命名块
    const n = parseU64("12a", 10) catch blk: {
        util.debugln("10进制错误", .{}); // catch不能捕获错误
        break :blk 3;
    };
    _ = n;

    // 错误处理
    if (parseU64("12f", 10)) |v| { // 捕获变量与外层同处于一个语句空间，命名不能重复，如：这里不能用num
        util.debugln("转换后的数字是：{}", .{v});
    } else |e| {
        util.debugln("转换错误：{any}", .{e});
    }
}
